#include "SocketServer.h"
#include "ErrorUtility.h"

SocketServer::SocketServer(int _port,ISocketServerDelegate& socketServerDelegate):
	rSocketServerDelegate(socketServerDelegate)
{
	WSADATA wsaData;
	int iResult=WSAStartup(MAKEWORD(2,2),&wsaData);
	if (iResult != 0)
		throw exception("Unable to initialize Winsock.");

	port=_port;
	listeningSocket=INVALID_SOCKET;
	socketMutex=CreateMutex(NULL,FALSE,NULL); 
}
SocketServer::~SocketServer(void)
{
	CloseAllConnections();
	CloseHandle(socketMutex);
	
	WSACleanup();
}
void SocketServer::Start()
{
	listeningThread=CreateThread(NULL,0,AcceptIncomingConnections,(LPVOID)this,CREATE_SUSPENDED,&listeningThreadId);
	ResumeThread(listeningThread);
}
void SocketServer::Wait()
{
	WaitForSingleObject(listeningThread,INFINITE);
}
void SocketServer::CloseAllConnections()
{
	shutdown(listeningSocket,SD_BOTH);
	closesocket(listeningSocket);


	WaitForSingleObject(socketMutex,INFINITE);
	
	for(size_t i=0;i<socketClients.size();i++)
	{
		shutdown(socketClients.at(i),SD_BOTH);
		closesocket(socketClients.at(i));
	}
	socketClients.clear();

	ReleaseMutex(socketMutex);
}

DWORD WINAPI SocketServer::AcceptIncomingConnections(LPVOID lpParam)
{
	SocketServer* pServer=(SocketServer*)lpParam; //refactor: use dynamic_cast<>

	addrinfo hints;
	addrinfo* result;
	ZeroMemory(&hints,sizeof(hints));
    hints.ai_family=AF_INET;
    hints.ai_socktype=SOCK_STREAM;
    hints.ai_protocol=IPPROTO_TCP;
    hints.ai_flags=AI_PASSIVE;

	stringstream ss;
	ss<<pServer->port;

	int iResult=getaddrinfo(NULL,ss.str().c_str(),&hints,&result);
    if(iResult!=0)
	{
		ErrorUtility::Instance().Error("unable to resolve local address");
		return 0;
    }

	pServer->listeningSocket=socket(result->ai_family,result->ai_socktype,result->ai_protocol);
    if(pServer->listeningSocket==INVALID_SOCKET)
	{
		ErrorUtility::Instance().Error("unable to initialize a listening socket");
        freeaddrinfo(result);
        return 0;
    }

    iResult=bind(pServer->listeningSocket,result->ai_addr,(int)result->ai_addrlen);
    if(iResult==SOCKET_ERROR)
	{
		ErrorUtility::Instance().Error("unable to bind to listening socket");
        freeaddrinfo(result);
        closesocket(pServer->listeningSocket);
        return 0;
    }

    freeaddrinfo(result);
    iResult=listen(pServer->listeningSocket,SOMAXCONN);
    if(iResult==SOCKET_ERROR)
	{
        ErrorUtility::Instance().Error("unable to listen on socket");
        closesocket(pServer->listeningSocket);
        WSACleanup();
        return 1;
    }

    while(true)
	{
		ErrorUtility::Instance().Info("listening for socket connections...");
		SOCKET sd=accept(pServer->listeningSocket,NULL,NULL);
		if(sd==INVALID_SOCKET)
		{
			closesocket(pServer->listeningSocket);
			break;
		}
		SocketClient* pClient=new SocketClient(pServer->rSocketServerDelegate);
		pClient->socket=sd;
		
		WaitForSingleObject(pServer->socketMutex,INFINITE);
		pServer->socketClients.push_back(sd);
		ReleaseMutex(pServer->socketMutex);
		
		pClient->thread=CreateThread(NULL,0,ListenToClient,(LPVOID)pClient,CREATE_SUSPENDED,&pClient->threadId);
		ResumeThread(pClient->thread);
	}
	closesocket(pServer->listeningSocket);
	return 0;
}
DWORD WINAPI SocketServer::ListenToClient(LPVOID lpParam)
{
	SocketClient* pSocketClient=(SocketClient*)lpParam;//refactor: use dynamic_cast<>
	SocketClient client(*pSocketClient);

	delete pSocketClient;

	const int bufferLength=4096;
	char buffer[bufferLength];
	
	while(true)
	{
		memset(buffer,0,bufferLength*sizeof(char));
		int iResult=recv(client.socket,buffer,bufferLength,0);
		if(iResult>0)
		{
			char* response;
			int responseLength;
			bool keepAlive=client.socketServerDelegate.OnData(buffer,iResult,&response,&responseLength);
			iResult=send(client.socket,response,responseLength,0);
			free(response);

			if(iResult==SOCKET_ERROR)
				break;
			if(!keepAlive)
				break;
		}	
	}
	shutdown(client.socket,SD_BOTH);
	closesocket(client.socket);
	return 0;
}